using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using Microsoft.VisualStudio.Modeling;
using EnvDTE80;
using DslFactory.Utilities.DteCodeModel;
using System.Net.Security;
using System.ServiceModel;

namespace Dslfactory.wcfLanguage
{
 /// <summary>
 /// Update the DataContract of the model from the code
 /// </summary>
 /// <param name="servicesDefinition">Root element of the model</param>
 /// <param name="knownCodeTypes">Known types</param>
 internal static class DataContractReverseEngineering
 {
  public static void UpdateDataContracts(ServicesDefinition servicesDefinition, KnownCodeTypes knownCodeTypes)
  {
   List<DataContract> dataContracts = new List<DataContract>(servicesDefinition.DataContracts); // Important (collection might be modified)
   foreach (DataContract dataContract in dataContracts)
   {
    CodeType codeType = knownCodeTypes.GetFullNamedType(dataContract.FullName);
    if (codeType != null)
     MergeDataContract(servicesDefinition, codeType);
   }
  }

  public static bool CanMerge(CodeType codeType)
  {
   if (codeType is CodeClass2)
    return (IncrementalGenerator.HasAttribute(codeType, "System.Runtime.Serialization.DataContractAttribute"));
   else
    return false;
  }


  internal static DataContract MergeType(ServicesDefinition ServicesDefinition, CodeType codeType)
  {
   if (codeType is CodeClass2)
    return MergeDataContract(ServicesDefinition, codeType);
   else
    return null;
  }

  private static DataContract MergeDataContract(ServicesDefinition ServicesDefinition, CodeType codeType)
  {
   Store Store = ServicesDefinition.Store;
   DataContract dataContract = ServicesDefinition.FindDataContractOfFullName(codeType.FullName);

   // Update the data contract
   using (Transaction tUpdate = Store.TransactionManager.BeginTransaction("Updating Data contract"))
   {
    // Add a data contract
    if (dataContract == null)
     {
      dataContract = new DataContract(Store);
      DataClassReverseEngineering.UpdateDataClassProperties(dataContract, codeType);
      ServicesDefinition.DataContracts.Add(dataContract);
     }

     if (IncrementalGenerator.HasAttribute(codeType, "System.Runtime.Serialization.DataContractAttribute"))
    {
     dataContract.IsDataContract = true;
     UpdateDataContractProperties(dataContract, codeType);
    }
    else
     dataContract.IsDataContract = false;


    // In every case, we need to update the operations
    CodeClass2 codeClass = codeType as CodeClass2;
     IncrementalGenerator.UpdateModelRoleFromCodeElements(dataContract.DataMembers,
                                                          IncrementalGenerator.GetFieldsAndProperties(codeClass),
                                                          DataMember.NameDomainPropertyId,
                                                          delegate(CodeElement e) { return e.Name; },
                                                          DataMember.DomainClassId,
                                                          UpdateDataMember);

    if (tUpdate.HasPendingChanges)
     tUpdate.Commit();
   }
   return dataContract;
  }



  /// <summary>
  /// Updates the datamember from information in the CodeElement.
  /// </summary>
  private static void UpdateDataMember(FieldOrProperty member, CodeElement e)
  {
   DataClassReverseEngineering.UpdateFieldOrProperty(member, e);

   DataMember m = member as DataMember;
   if (m == null)
    return;
   if (IncrementalGenerator.HasAttribute(e, "DataMember"))
   {
    m.IsDataMember = true;
    UpdateDataMemberProperties(m, e);
   }
   else
    m.IsDataMember = false;
  }

  /// <summary>
  /// Update 
  /// </summary>
  /// <param name="m"></param>
  /// <param name="e"></param>
  public static void UpdateDataMemberProperties(DataMember m, CodeElement e)
  {
   CodeAttribute2 attribute = IncrementalGenerator.FindFirstAttribute(e, "System.Runtime.Serialization.DataMemberAttribute");
   if (attribute != null)
   {
    List<string> notFound = new List<string>(new string[]
     {
      "Name", "IsRequired", "Order", "EmitDefaultValue"
     });

    foreach (CodeAttributeArgument argument in attribute.Arguments)
    {
     notFound.Remove(argument.Name);
     switch (argument.Name)
     {
      case "Name":
       m.DataMemberName = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;

      case "IsRequired":
       try
       {
        m.IsRequired = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "Order":
       try
       {
        m.Order = int.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "EmitDefaultValue":
       try
       {
        m.EmitDefaultValue = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;
     }
    }

    // All the argument that where not found must be reset to their default value
    foreach (string argumentName in notFound)
     switch (argumentName)
     {
      case "Name":
       m.DataMemberName = string.Empty;
       break;

      case "IsRequired":
       m.IsRequired = true;
       break;

      case "Order":
       m.Order = -1;
       break;

      case "EmitDefaultValue":
       m.EmitDefaultValue = true;
       break;
     }
   }
  }


  /// <summary>
  /// Update the properties of the data contract
  /// </summary>
  /// <param name="dataContract"></param>
  /// <param name="codeType"></param>
  public static void UpdateDataContractProperties(DataContract dataContract, CodeType codeType)
  {
   List<string> notFound = new List<string>(new string[]
    {
     "Name", "Namespace"
    });
   // Get information from the attribute
   CodeAttribute2 attribute = IncrementalGenerator.FindFirstAttribute(codeType, "System.Runtime.Serialization.DataContractAttribute");
   if (attribute != null)
    foreach (CodeAttributeArgument argument in attribute.Arguments)
    {
     notFound.Remove(argument.Name);
     switch (argument.Name)
     {
      case "Name":
       dataContract.DataContractName = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
      case "Namespace":
       dataContract.DataContractNamespace = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
     }
    }

   // Default value for arguments that are not found
   foreach (string argumentName in notFound)
   {
    switch (argumentName)
    {
     case "Name":
      dataContract.DataContractName = string.Empty;
      break;
     case "Namespace":
      dataContract.DataContractNamespace = string.Empty;
      break;
    }
   }
  }

  internal static bool CanProcessAttributeAddedOrChanged(CodeAttribute2 attribute)
  {
   return IsDataContractAttribute(attribute) || IsDataMemberAttribute(attribute);
  }

  internal static void ProcessAttributeAddedOrChanged(ServicesDefinition servicesDefinition, CodeAttribute2 attribute)
  {
   // Data Contact
   if (IsDataContractAttribute(attribute))
   {
    CodeType codeClass = attribute.Parent as CodeType;
    if (codeClass != null)
    {
     DataContract dataContact = servicesDefinition.FindDataContractOfFullName(codeClass.FullName);
     if (dataContact == null)
      return;

     using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Reversing attribute '{0}' on class '{1}'", attribute.Name, codeClass.FullName)))
     {
      dataContact.IsDataContract = true;
      DataContractReverseEngineering.UpdateDataContractProperties(dataContact, codeClass);
      if (transaction.HasPendingChanges)
       transaction.Commit();
     }
    }
   }

   // DataMember
   else if (IsDataMemberAttribute(attribute))
   {
    CodeElement codeElement = attribute.Parent as CodeElement;
    if (codeElement == null)
     return;
    if ((codeElement.Kind != vsCMElement.vsCMElementProperty) && (codeElement.Kind != vsCMElement.vsCMElementVariable))
     return;

    CodeType codeType = codeElement.Collection.Parent as CodeType;
    if (codeType == null)
     return;

    DataContract dataContract = servicesDefinition.FindDataContractOfFullName(codeType.FullName);
    if (dataContract == null)
     return;

    DataMember dataMember = dataContract.FindMemberOfName(codeElement.Name) as DataMember;
    if (dataMember != null)
     using (Transaction transaction = dataMember.Store.TransactionManager.BeginTransaction("Update data member properties"))
     {
      dataMember.IsDataMember = true;
      DataContractReverseEngineering.UpdateDataMemberProperties(dataMember, codeElement);
      if (transaction.HasPendingChanges)
       transaction.Commit();
     }
   }
  }

  public static bool IsDataMemberAttribute(CodeAttribute2 attribute)
  {
   return (attribute.FullName == "System.Runtime.Serialization.DataMemberAttribute") || (attribute.FullName == "System.ServiceModel.DataMember");
  }

  public static bool IsDataContractAttribute(CodeAttribute2 attribute)
  {
   return (attribute.FullName == "System.Runtime.Serialization.DataContractAttribute") || (attribute.FullName == "System.Runtime.Serialization.DataContract");
  }

  public static void ProcessDataMemberAttributeRemoved(CodeElement parent, ServicesDefinition servicesDefinition)
  {
   CodeElement codeElement = parent as CodeElement;
   if (codeElement == null)
    return;
   if ((codeElement.Kind != vsCMElement.vsCMElementProperty) && (codeElement.Kind != vsCMElement.vsCMElementVariable))
    return;

   CodeType codeType = codeElement.Collection.Parent as CodeType;
   if (codeType == null)
    return;

   DataContract dataContract = servicesDefinition.FindDataContractOfFullName(codeType.FullName);
   if (dataContract == null)
    return;

   DataMember dataMember = dataContract.FindMemberOfName(codeElement.Name) as DataMember;
   if (dataMember != null)
    using(Transaction transaction = dataMember.Store.TransactionManager.BeginTransaction("transforming to data member"))
    {
     dataMember.IsDataMember = false;
     if (transaction.HasPendingChanges)
      transaction.Commit();
    }
   return;
  }

  public static bool IsDataMemberAttribute(string attributeName)
  {
   return (attributeName == "System.Runtime.Serialization.DataMemberAttribute")
            || (attributeName == "System.Runtime.Serialization.DataMember")
            || (attributeName == "DataMemberAttribute")
            || (attributeName == "DataMember")
           ;
  }

  public static void ProcessDataContractAttributeRemoved(CodeElement parent, ServicesDefinition servicesDefinition)
  {
   CodeClass codeInterface = parent as CodeClass;
   if (codeInterface != null)
   {
    DataContract iface = servicesDefinition.FindDataContractOfFullName(codeInterface.FullName) as DataContract;
    if (iface != null)
     using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Transforming Data contract '{0}' into a simple class", iface.FullName)))
     {
      iface.IsDataContract = false;
      transaction.Commit();
     }
   }
  }

  public static bool IsDataContractAttribute(string attributeName)
  {
   return (attributeName == "System.Runtime.Serialization.DataContractAttribute")
          || (attributeName == "System.Runtime.Serialization.DataContract")
          || (attributeName == "DataContractAttribute")
          || (attributeName == "DataContract")
         ;
  }
 }
}
